/* Custom USB HID Communication Device
 * Copyright (c) Secret Labs LLC. All Rights Reserved.
 * 
 * Full Keyboard expansion added by Omar Zabala.
 *
 * Licensed under the Apache 2.0 open source license
 */


using System;
using Microsoft.SPOT;
using System.Threading;
using Microsoft.SPOT.Hardware.UsbClient;

namespace Netduino_USB_HID_Library
{
    public static class Keyboard
    {
        const int WRITE_ENDPOINT = 1;
        const int MAXPOWER_MILLIAMPS = 200;
        const int HID_DESCRIPTOR_TYPE = 0x21;

        public static UsbStream stream;
        public static UsbController usbController;
        public static bool started = false;

        static bool _overide = false;
        static byte _mod = 0x00;
        static int pressWait = 0, releaseWait = 0;
        public static string holdDownKeys = "";

        public enum ArrowKeys
        {
            LeftArrow = 27,
            UpArrow = 24,
            RightArrow = 26,
            DownArrow = 25
        }
        // Note some computers might not support some of these. 
        public enum Modifiers
        {
            None = 0,
            LeftCtrl = 1,
            LeftShift = 2,
            LeftCtrlShift = 3,
            LeftAlt = 4,
            LeftGUI = 8,
            RightCtrl = 16,
            RightShift = 32,
            RightCtrlShift = 48,
            RightAlt = 64,
            RightGUI = 128,
        }

        public static string SetUp()
        {
            UsbController[] usbControllers = UsbController.GetControllers();

            if (usbControllers.Length < 1)
                return "Error: No controllers found.";

            usbController = usbControllers[0];

            try
            {
                Configure(usbController);

                if (!usbController.Start())
                    return "Error: Could not start controller.";

                stream = usbController.CreateUsbStream(WRITE_ENDPOINT, UsbStream.NullEndpoint);
            }
            catch (Exception e)
            { return "Error:\n" + e.Message; }
            
            started = true;
            return "Success";
        }

        static void Configure(UsbController usbController)
        {
            Configuration configuration = new Configuration();
            Configuration.DeviceDescriptor deviceDescriptor = new Configuration.DeviceDescriptor(0x22B1, 0xFFFF, 0x0100); // VID, PID, version -- if you change the PID (0xFFFF) be sure to change the VID.

            deviceDescriptor.bcdUSB = 0x110;
            deviceDescriptor.bDeviceClass = 0;
            deviceDescriptor.bDeviceSubClass = 0;
            deviceDescriptor.bDeviceProtocol = 0;
            deviceDescriptor.bMaxPacketSize0 = 8;
            deviceDescriptor.iManufacturer = 1; // String #1 is the manufacturer name.
            deviceDescriptor.iProduct = 2;      // String #2 is the product name.
            deviceDescriptor.iSerialNumber = 0;

            byte[] hidClassDescriptorPayload = new byte[] 
            {
                0x01, 0x11,     // bcdHID (v1.01) || 0x01, 0x01, changed to 0x01, 0x11, 
                0x00,           // bCountryCode
                0x01,           // bNumDescriptors
                0x22,           // bDescriptorType (report)
                0x43, 0x00      // wDescriptorLength (report descriptor size in bytes)
            };

            // HID class descriptor
            Configuration.ClassDescriptor hidClassDescriptor = new Configuration.ClassDescriptor(HID_DESCRIPTOR_TYPE, hidClassDescriptorPayload);
            // write endpoint
            Configuration.Endpoint writeEndpoint = new Configuration.Endpoint(WRITE_ENDPOINT, Configuration.Endpoint.ATTRIB_Interrupt | Configuration.Endpoint.ATTRIB_Write);
            writeEndpoint.wMaxPacketSize = 16;   // packet size: 16 / 32 bytes
            writeEndpoint.bInterval = 10;         // interval: 10ms
            Configuration.UsbInterface usbInterface = new Configuration.UsbInterface(0, new Configuration.Endpoint[] { writeEndpoint });
            usbInterface.classDescriptors = new Configuration.ClassDescriptor[] { hidClassDescriptor };
            usbInterface.bInterfaceClass = 0x03;
            usbInterface.bInterfaceSubClass = 0x00;
            usbInterface.bInterfaceProtocol = 0x00; // keyboard

            // configuration descriptor
            Configuration.ConfigurationDescriptor configurationDescriptor = new Configuration.ConfigurationDescriptor(MAXPOWER_MILLIAMPS, new Configuration.UsbInterface[] { usbInterface });
            configurationDescriptor.bmAttributes = Configuration.ConfigurationDescriptor.ATTRIB_Base | Configuration.ConfigurationDescriptor.ATTRIB_SelfPowered;

            // HID report descriptor
            byte[] hidGenericReportDescriptorPayload = new byte[] 
            {
                0x05, 0x01,                    // USAGE_PAGE (Generic Desktop)
                0x09, 0x06,                    // USAGE (Keyboard)

                0xA1, 0x01,                    // COLLECTION (Application)

                0x85, 0x01,                    //   REPORT_ID (1)
                0x05, 0x07,                    //   USAGE PAGE (Keyboard)
                0x19, 0xE0,                    //   USAGE_MINIMUM (Keyboard LeftControl)
                0x29, 0xE7,                    //   USAGE_MAXIMUM (Keyboard Right GUI)
                0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
                0x25, 0x01,                    //   LOGICAL_MAXIMUM (1)
                0x75, 0x01,                    //   REPORT_SIZE (1)
                0x95, 0x08,                    //   REPORT_COUNT (8)
                0x81, 0x02,                    //   INPUT (Data,Var,Abs)
                0x95, 0x01,                    //   REPORT_COUNT (1)
                0x75, 0x08,                    //   REPORT_SIZE (8)
                0x81, 0x01,                    //   INPUT (Const,Ary,Abs)
                0x95, 0x03,                    //   REPORT_COUNT (3)
                0x75, 0x01,                    //   REPORT_SIZE (1)
                0x05, 0x08,                    //   USAGE_PAGE (LEDs)
                0x19, 0x01,                    //   USAGE_MINIMUM (Num Lock)
                0x29, 0x03,                    //   USAGE_MAXIMUM (Scroll Lock)
                0x91, 0x02,                    //   OUTPUT (Data,Var,Abs)
                0x95, 0x01,                    //   REPORT_COUNT (1)
                0x75, 0x05,                    //   REPORT_SIZE (5)
                0x91, 0x01,                    //   Output (Cnst,Ary,Abs)
                0x95, 0x06,                    //   REPORT_COUNT (6)
                0x75, 0x08,                    //   REPORT_SIZE (8)
                0x15, 0x00,                    //   LOGICAL_MINIMUM (0)
                0x26, 0xFF, 0x00,              //   LOGICAL_MAXIMUM (255)
                0x05, 0x07,                    //   USAGE_PAGE (Keyboard)
                0x19, 0x00,                    //   USAGE_MINIMUM (Reserved (no event indicated))
                0x2A, 0xFF, 0x00,              //   USAGE_MAXIMUM (Keyboard Application, 255)
                0x81, 0x00,                    //   INPUT (Data,Ary,Abs)

                0xC0,                           // END_COLLECTION
            };

            Configuration.GenericDescriptor hidGenericReportDescriptor = new Configuration.GenericDescriptor(0x81, 0x2200, hidGenericReportDescriptorPayload);
            hidGenericReportDescriptor.bRequest = 0x06; // GET_DESCRIPTOR
            hidGenericReportDescriptor.wIndex = 0x00; // INTERFACE 0 (zero)

            Configuration.StringDescriptor manufacturerNameStringDescriptor = new Configuration.StringDescriptor(1, "Secet Labs LLC");
            Configuration.StringDescriptor productNameStringDescriptor = new Configuration.StringDescriptor(2, "Netduino Keyboard");
            Configuration.StringDescriptor displayNameStringDescriptor = new Configuration.StringDescriptor(4, "Netduino Keyboard");
            Configuration.StringDescriptor friendlyNameStringDescriptor = new Configuration.StringDescriptor(5, "Netduino Keyboard");

            configuration.descriptors = new Configuration.Descriptor[]
            {
                deviceDescriptor,
                configurationDescriptor, 
                manufacturerNameStringDescriptor, 
                productNameStringDescriptor,
                displayNameStringDescriptor,
                friendlyNameStringDescriptor,
                hidGenericReportDescriptor
            };

            usbController.Configuration = configuration;
        }

        /* Supported Characters
         * a-z
         * A-Z
         * 0-9
         * enter escape tab space backspace
         * - = [ { ] } \ | , > . < / ? ! @ # $ % ^ & * ( )
         */
        public static void Send(char c)
        {
            byte[] res = GetByte(c);

            if (!_overide)
                _mod = res[1];

            Send(res[0]);
        }

        public static void Send(int FunctionKey)
        {
            _mod = 0x00;

            if (FunctionKey > 0 && FunctionKey < 13) // F1-F12
                FunctionKey += 57;
            else if (FunctionKey > 12 && FunctionKey < 25) // Apparently some keyboards can do F13-F24
                FunctionKey += 91;
            else
                FunctionKey = 0;

            Send((byte)FunctionKey);
        }

        public static void Send(ArrowKeys arrow)
        {
            Send((char)arrow);
        }

        public static void Send(string s)
        {
            foreach (char c in s.ToCharArray())
                Send(c);
        }

        public static void Hold(ArrowKeys arrow)
        {
            press((char)arrow);
        }

        public static void Release(ArrowKeys arrow)
        {
            Release((char)arrow);
        }

        public static void Hold(char c)
        {
            if (holdDownKeys.IndexOf(c) != -1)
                return;

            holdDownKeys += c;

            press(c);
        }
        
        public static void Release(char c)
        {
            if (holdDownKeys.IndexOf(c) == -1)
                return;

            char[] keys = new char[holdDownKeys.Length - 1];

            if (keys.Length == 0)
                return;

            int i = 0;
            foreach(char n in holdDownKeys.ToCharArray())
                if (n != c)
                {
                    keys[i] = n;
                    i++;
                }

            holdDownKeys = new string(keys);

            stream.Write(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 9);

            if (holdDownKeys != "")
                press(holdDownKeys[holdDownKeys.Length-1]);
        }

        static void press(char c)
        {
            byte[] res = GetByte(c);

            if (!_overide)
                _mod = res[1];

            stream.Write(new byte[] { 0x01, _mod, 0x00, res[0], 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 9);
        }

        public static void Send(byte b)
        {
            if (_overide) 
                _overide = false;

            stream.Write(new byte[] { 0x01, _mod, 0x00, b, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 9);
            Thread.Sleep(pressWait);
            stream.Write(new byte[] { 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, 0, 9);
            Thread.Sleep(releaseWait);
        }

        public static void SetModifier(Modifiers mod)
        {
            _overide = true;
            _mod = (byte)mod;
        }

        public static byte[] GetByte(char c)
        {
            byte cb = (byte)c;
            byte mod = 0x00;

            #region determine key value
            if (cb < 123 && cb > 96)        // a-z
                cb -= 93;
            else if (cb < 91 && cb > 64)    // A-Z
            {
                cb -= 61;                   // make it the right letter code as lower case
                mod = 0x02;                   // add a modifier, shift key in this case
            }
            else if (cb < 58 && cb > 47)    // 0-9
            {
                if (cb == 48)
                    cb = 39;
                else
                    cb -= 19;
            }
            else if(cb > 23 && cb < 28)
            {
                if (cb == 24)
                    cb = 82;
                else if (cb == 25)
                    cb = 81; 
                else if (cb == 26)
                    cb = 79; 
                else
                    cb = 80;
            }
            else
            {
                #region Special keys switch statement
                switch (cb)
                {
                    case 10:        // Return or Enter
                        cb = 40;
                        break;
                    case 27:        // Escape
                        cb = 41;
                        break;
                    case 8:         // Delete
                        cb = 42;
                        break;
                    case 9:         // Tab
                        cb = 43;
                        break;
                    case 32:        // Space                  
                        cb = 44;
                        break;
                    case 45:        // -
                        cb = 45;
                        break;
                    case 95:        // _
                        cb = 45;
                        mod = 0x02;
                        break;
                    case 61:        // =
                        cb = 46;
                        break;
                    case 43:        // +
                        cb = 46;
                        mod = 0x02;
                        break;
                    case 91:        // [
                        cb = 47;
                        break;
                    case 123:       // {
                        cb = 47;
                        mod = 0x02;
                        break;
                    case 93:        // ]
                        cb = 48;
                        break;
                    case 125:       // }
                        cb = 48;
                        mod = 0x02;
                        break;
                    case 92:        // \
                        cb = 49;
                        break;
                    case 124:       // |
                        cb = 49;
                        mod = 0x02;
                        break;
                    case 59:       // ;
                        cb = 51;
                        break;
                    case 58:        // :
                        cb = 51;
                        mod = 0x02;
                        break;
                    case 44:        // ,
                        cb = 54;
                        break;
                    case 60:        // <
                        cb = 54;
                        mod = 0x02;
                        break;
                    case 46:        // .
                        cb = 55;
                        break;
                    case 62:        // >
                        cb = 55;
                        mod = 0x02;
                        break;
                    case 47:        // /
                        cb = 56;
                        break;
                    case 63:        // ?
                        cb = 56;
                        mod = 0x02;
                        break;
                    case 33:        // !
                        cb = 30;
                        mod = 0x02;
                        break;
                    case 64:        // @
                        cb = 31;
                        mod = 0x02;
                        break;
                    case 35:        // #
                        cb = 32;
                        mod = 0x02;
                        break;
                    case 36:        // $
                        cb = 33;
                        mod = 0x02;
                        break;
                    case 37:        // %
                        cb = 34;
                        mod = 0x02;
                        break;
                    case 94:        // ^
                        cb = 35;
                        mod = 0x02;
                        break;
                    case 38:        // &
                        cb = 36;
                        mod = 0x02;
                        break;
                    case 42:        // *
                        cb = 37;
                        mod = 0x02;
                        break;
                    case 40:        // (
                        cb = 38;
                        mod = 0x02;
                        break;
                    case 41:        // )
                        cb = 39;
                        mod = 0x02;
                        break;
                    default:        // Not supported, send blank.
                        cb = 0;
                        break;
                }
                #endregion
            }
            #endregion

            return new byte[] { cb, mod };
        }

        public static int WaitAfterPress
        {
            get { return pressWait; }
            set { pressWait = value; }
        }

        public static int WaitAfterRelease
        {
            get { return releaseWait; }
            set { releaseWait = value; }
        }
    }
}
